<html>
	<head>
		<script src="./random.js"></script>
		<script language="javaScript">
			let randomFn;
			const knownClasses = {
				"CanvasRenderingContext2D": {
					methods: [],
					fields: [],
					found: {
						// Blacklist
						fillRect: true
					},
					outputs: []
				}
				
			};

			let canvas, context;
			const exampleOutputs = [];
			let tryMethodCount = 0;
			let successMethodCount = 0;
			let failedMethodCount = 0;
			let logList = "";

			function log(text) {
				logList = "* " + text + "\n" + logList;
				document.getElementById("logs").innerHTML = logList;
			}

			function exploreObject(object) {
				const className = object.constructor.name;

				// Create an enty for this class
				if ( !knownClasses[className] ) {
					knownClasses[className] = {
						methods: [],
						fields: [],
						found: {},
						outputs: []
					}
				}

				for ( const key in object ) {
					// Make sure we don't look at one object twice
					if ( knownClasses[className].found[key] ) {
						continue;
					}
					
					// Declare found classes
					knownClasses[className].found[key] = true;

					if ( typeof object[key] == 'function' ) {
						// TODO - make available but less common
						if ( object[key].length == 0 ) {
							continue;
						}

						// Found a function 
						knownClasses[className].methods.push({
							name: key,
							args: object[key].length
						});
					}
					else {
						// Found a field
						knownClasses[className].fields.push({
							name: key,
							type: typeof object[key]
						});
					}
				}
			}

			// All the generators
			let num_generators;
			let void_generators;
			let str_generators;
			let list_generators;
			let other_generators;
			let all_generators;

			// Generate the fuzzers
			function initFuzzGenerators() {
				num_generators = [
					() => 1,
					() => -1,
					() => -1.000000001,
					() => -100000000,
					() => 100000000,
					() => true,
					() => false,
					() => randomFn() * 1000000,
					randomFn
				];

				void_generators = [
					() => null,
					() => undefined,
					() => NaN,
				]

				str_generators = [
					() => 'pink',
					() => 'blue',
					() => 'black',
					() => 'https://i.imgur.com/PqntHMn.png',
					() => "",
					() => "hello my name is fuzzio",
					() => '#abc',
					() => '#abcdef',
					() => {
						let a = "";

						for ( let i = 0; i < 1000000; i++ ) {
							a += "A";		
						}

						return a;
					}
				];

				list_generators = [
					() => [],
					() => [1],
					() => {
						let l = [];

						for ( let i = 0; i < Math.floor(randomFn() * 10); i++ ) {
							l.push(generateFuzzValue());
						}

						return l
					},
					() => {
						return generateMultipleFuzzValue(Math.floor(randomFn() * 20), className).join(' ');
					}
				];

				other_generators = [
					() => {
						const exampleOutputs = knownClasses[className].outputs;

						if ( exampleOutputs.length == 0 ) {
							return 0;
						}

						return exampleOutputs[Math.floor(randomFn() * exampleOutputs.length)];
					},
					() => {
						return () => {};
					},
					() => {
						return () => {
							return generateFuzzValue();
						}
					},
					() => new Uint8Array(Math.floor(randomFn() * 1000000)),
					() => new Image()
				];

				all_generators = num_generators.concat(void_generators)
											   .concat(str_generators)
											   .concat(list_generators)
											   .concat(other_generators);
			}


			// Generate a value to fuzz a function with
			function generateFuzzValue(className) {
				const rand = Math.floor(randomFn() * all_generators.length);
				return all_generators[rand]();
			}

			// Generate N fuzz values
			function generateMultipleFuzzValue(amt, className) {
				const l = [];

				for ( let i = 0; i < amt; i++ ) {
					l.push(generateFuzzValue(className));
				}

				return l;
			}

			// Generate random values and pas them to the fields
			// TODO - mainpulate values aswell
			function fuzzRandomField(obj, className) {
				const fields = knownClasses[className].fields;
				const field = fields[Math.floor(randomFn() * fields.length)];

				log("Fuzzing field " + className + "." + field.name);

				obj[field.name] = generateFuzzValue(className);
			}

			// Generate random values and pass them to the function
			function fuzzRandomMethod(obj, className) {
				const methods = knownClasses[className].methods;
				const method = methods[Math.floor(randomFn() * methods.length)];

				log("Fuzzing method " + className + "." + method.name);

				tryMethodCount += 1;
				try {
					let amt = method.args;

					// Occassionally clip off the end
					if ( randomFn() > 0.9 ) {
						amt = Math.floor(amt * randomFn()) + 1;
					}

					const args = generateMultipleFuzzValue(amt, className);
					const output = obj[method.name].apply(obj, args);

					if ( output != undefined ) {
						knownClasses[className].outputs.push(output); // TODO - per class this
					}

					successMethodCount += 1;
				}
				catch ( err ) {
					failedMethodCount += 1;
				}
			}


			function fuzzObject(obj) {
				const className = obj.constructor.name;

				if ( !knownClasses[className] ) {
					return;
				}

				try {
					// This can be changed to get different results
					if ( randomFn() > 0.75 ) {
						fuzzRandomField(obj, className);
					}
					else {
						fuzzRandomMethod(obj, className);
					}
				}
				catch (err) {

				}
			}

			function initFuzz(type) {
				initFuzzGenerators();

				// Load the context object to fuzz
				if ( type == "canvas" ) {
					canvas = document.getElementById('cnv');
					context = canvas.getContext('2d');
				}
				else if ( type == "webaudio" ) {
					context = new (window.AudioContext || window.webkitAudioContext)();
				}

				// Generate a seed and pass to the random number generator
				let seed = Math.floor(Math.random() * 10000);
				document.getElementById('seed').innerHTML = seed;
				randomFn = Math.seed(seed);

				// Explore the object
				exploreObject(context);
				
				let fuzzOps = 0;

				// Idk why this is 10.
				for ( let i = 0; i < 10; i++ ) {
					window.setInterval(() => {
						fuzzObject(context);
						fuzzOps += 1;
					}, 1);
				}

				// Interface interval
				window.setInterval(() => {
					document.getElementById("fops").innerHTML = fuzzOps.toString();
					document.getElementById("success").innerHTML = ((successMethodCount / tryMethodCount) * 100).toFixed(2);
						

					// Clip the logs so nothing crazy happens
					logList = logList.substring(0, 2000);

					fuzzOps = 0;
					successMethodCount = 0;
					failedMethodCount = 0;
					tryMethodCount = 0;
				}, 1000);
			}
		</script>
	</head>

	<body onload="initFuzz('canvas')">
		<h2>Avi's HTML5 Canvas Fuzzer</h2>
		<hr>
		<p>
			Currently doing <b><span id="fops">...</span></b> FOPS<br>
			Currently running at <b><span id="success">...</span></b>% success<br>
			Currently using seed <b><span id="seed">...</span></b>
		</p>

		<p>
			<b>Current HTML5 canvas output</b><br>
			<canvas width="100" height="100" id="cnv" style="border-style: solid;"></canvas>
		</p>

		<p>
			<b>Logs</b><br>
			<textarea id="logs" rows=30 cols=60 style="border-style: solid;"></textarea>
		</p>
	</body>
</html>